﻿using IOP.SgrA;
using IOP.SgrA.SilkNet.Vulkan;
using Microsoft.Extensions.Logging;
using Silk.NET.Vulkan;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Semaphore = Silk.NET.Vulkan.Semaphore;

namespace VkSample1213
{
    [ModulePriority(ModulePriority.RenderGroup)]
    public class PipelineModule : VulkanModule, IVulkanRenderWapperModule
    {
        private readonly ILogger<PipelineModule> _Logger;
        public PipelineModule(ILogger<PipelineModule> logger)
        {
            _Logger = logger;
        }

        public VulkanRenderWapper RenderWapper { get; set; }

        public SecondaryVulkanRenderGroup LandGroup { get; private set; }

        protected override Task Load(VulkanGraphicsManager graphicsManager)
        {
            try
            {
                var lDevice = graphicsManager.VulkanDevice;
                var pipeCache = lDevice.PipelineCache;
                var disapatcher = RenderWapper.GetRenderDispatcher<VulkanSwapchainRenderDispatcher>();
                var presentPass = disapatcher.SwapchainRenderPass;
                var area = RenderWapper.RenderArea;
                var basePath = AppContext.BaseDirectory;
                var fence = lDevice.CreateFence();

                var camera = graphicsManager.CreateCamera("main", new Vector3(0, 200.0f, 250), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
                camera.EncircleForX(Vector3.Zero, -15 * MathF.PI / 180.0f);
                float aspcet = (float)area.Width / area.Height;
                camera.SetProjectMatrix(-aspcet, 1.0f, 1.5f, 1000, reversedZ: true);
                camera.SetViewport(0, 0, area.Width, area.Height, 0.0f, 1.0f);

                var pCommand = lDevice.CreateCommandPool((device, option) =>
                {
                    option.Flags = CommandPoolCreateFlags.ResetCommandBufferBit;
                    option.QueueFamilyIndex = device.WorkQueues[0].FamilyIndex;
                }).CreatePrimaryCommandBuffer();
                var mainGroup = graphicsManager.CreatePrimaryRenderGroup("Main", null) 
                    .Binding(pCommand, lDevice, presentPass, [disapatcher.ImageAvailableSemaphore, disapatcher.RenderFinishSemaphore], [fence.RawHandle]);
                mainGroup.CreateGroupRenderingAction((builder) => builder.Run(group => { PrimaryGroupMain(group); }));
                var pipeline = graphicsManager.BuildScriptedShaderAndPipeline<LandPipe>(graphicsManager.NewStringToken(), presentPass, pipeCache, area);
                var sCommand = lDevice.CreateCommandPool((device, option) =>
                {
                    option.Flags = CommandPoolCreateFlags.ResetCommandBufferBit;
                    option.QueueFamilyIndex = device.WorkQueues[0].FamilyIndex;
                }).CreateSecondaryCommandBuffer();
                var sGroup = graphicsManager.CreateSecondaryVulkanRenderGroup("Land", pipeline) 
                    .Binding(mainGroup, sCommand, [], []);
                sGroup.SetCamera(camera);
                sGroup.CreateGroupRenderingAction((builder) => builder.Run((group) => { LandGroupMain(group); }));
                LandGroup = sGroup;
                RenderWapper.CreateAndCutScene<Scene>("Main", mainGroup, typeof(InputSystem));
            }
            catch (Exception e)
            {
                _Logger.LogError(e, "");
            }
            return Task.CompletedTask;
        }

        protected override Task Unload(VulkanGraphicsManager manager)
        {
            return Task.CompletedTask;
        }

        private void PrimaryGroupMain(PrimaryVulkanRenderGroup group)
        {
            try
            {
                var queue = group.WorkQueues[0].Queue;
                var cmdBuffer = group.PrimaryCommandBuffer;
                var semaphores = group.Semaphores;
                var pass = group.RenderPass;
                var face = group.Fences;
                var lDevice = group.LogicDevice;

                VulkanFrameBuffer framebuffer = group.RenderPass.GetFramebuffer(group.CurrentFrame);
                cmdBuffer.Reset();
                cmdBuffer.Begin();
                cmdBuffer.BeginRenderPass(pass, framebuffer, pass.BeginOption, SubpassContents.SecondaryCommandBuffers);
                group.SecondaryGroupRendering(pass, framebuffer);
                cmdBuffer.ExecuteCommands(group.GetUpdateSecondaryBuffers());
                cmdBuffer.EndRenderPass();
                cmdBuffer.End();

                var submitInfo = new SubmitOption
                {
                    WaitDstStageMask = [PipelineStageFlags.ColorAttachmentOutputBit],
                    WaitSemaphores = [semaphores[0]],
                    Buffers = [cmdBuffer.RawHandle],
                    SignalSemaphores = [semaphores[1]]
                };
                lDevice.Submit(queue, face[0], submitInfo);
                Result r = Result.Timeout;
                do
                {
                    r = group.LogicDevice.WaitForFences(face, true, 100000000);
                } while (r == Result.Timeout);
                group.LogicDevice.ResetFences(face);
            }
            catch (Exception e)
            {
                group.Logger.LogError(e.Message + "\r\n" + e.StackTrace);
                group.Disable();
            }
        }


        int operatorT = 1;
        float span = 0.009f * 3.1415926f;
        float uFactor = 0.0f;
        float maxFactor = 2.0f * 3.1415926f;
        private void LandGroupMain(SecondaryVulkanRenderGroup group)
        {
            uFactor += operatorT * span;
            if (uFactor > maxFactor) uFactor = 0;
            var camera = group.Camera;
            var view = camera.ViewMatrix;
            var project = camera.ProjectionMatrix;
            var cmd = group.SecondaryCommandBuffer;

            CommandBufferInheritanceInfo info = new CommandBufferInheritanceInfo
            {
                Framebuffer = group.Framebuffer.RawHandle,
                OcclusionQueryEnable = false,
                RenderPass = group.RenderPass.RawHandle,
                Subpass = 0
            };
            cmd.Reset();
            cmd.Begin(CommandBufferUsageFlags.OneTimeSubmitBit |
                CommandBufferUsageFlags.RenderPassContinueBit, info);
            cmd.BindPipeline(PipelineBindPoint.Graphics, group.Pipeline);

            var viewPort = camera.Viewport;
            cmd.SetViewport(new Viewport(viewPort.X, viewPort.Y, viewPort.Width, viewPort.Height, viewPort.MinDepth, viewPort.MaxDepth));
            cmd.SetScissor(new Rect2D(null, new Extent2D((uint)viewPort.Width, (uint)viewPort.Height)));

            foreach (var item in group.GetContexts())
            {
                item.SetViewMatrix(in view);
                item.SetProjectionMatrix(in project);
                item.SetCamera(camera);
                var render = item.GetContextRenderObject();
                var pCamera = new Vector4(item.Camera.EyePosition, 1.0f);
                var pipeline = item.VulkanPipeline;
                int size = 156;
                ref MVPMatrix local = ref item.GetMVPMatrix();
                Matrix4x4 mat = local.GetFinalMatrix();
                byte[] data = ArrayPool<byte>.Shared.Rent(size);
                Span<byte> span = data;
                span = span.Slice(0, size);
                float landHighAdjust = 0.0f;
                float landHighest = 300.0f;
                mat.ToBytes(ref span, 0);
                MemoryMarshal.Write(span[64..128], in local.ModelMatrix);
                MemoryMarshal.Write(span[128..144], in pCamera);
                MemoryMarshal.Write(span[144..148], in uFactor);
                MemoryMarshal.Write(span[148..152], in landHighAdjust);
                MemoryMarshal.Write(span[152..156], in landHighest);
                var buffer = render.GetComponents<VulkanTexture>().First(x => x.SetIndex == 0);
                var tex = render.GetComponents<VulkanTexture>().First(x => x.SetIndex == 1);
                buffer.UpdateTextureMemoryData(span);

                var vro = render.GetComponents<VRO>().First();

                DescriptorSet[] sets = [buffer.DescriptorSet, tex.DescriptorSet];
                cmd.BindDescriptorSets(PipelineBindPoint.Graphics, pipeline.PipelineLayout, 0, null, sets);
                cmd.BindVertexBuffers(0, [0], vro.VerticesBufferInfo.Buffer);
                cmd.Draw(vro.VecticesCount, 1, 0, 0);
                ArrayPool<byte>.Shared.Return(data);
            }
            cmd.End();
        }
    }
}
